package com.seer.srd.qingdao

import com.seer.srd.BusinessError
import com.seer.srd.robottask.*
import com.seer.srd.storesite.StoreSiteService
import org.slf4j.LoggerFactory
import java.util.concurrent.Executors
import java.util.concurrent.TimeUnit

object NewTaskListener {
    private val logger = LoggerFactory.getLogger(NewTaskListener::class.java)

    private val executors = Executors.newSingleThreadScheduledExecutor()

    // 执行看板核对的库位的名称
    private const val boardCheckSiteId = "Check01"

    const val taskDefaultPriority = 0

    private val taskDefNameMatTransfer = CUSTOM_CONFIG.taskDefNameMatTransfer

    val taskDefNameMatInStoreA = CUSTOM_CONFIG.taskDefNameMatInStoreA

    val taskDefNameMatInStoreB = CUSTOM_CONFIG.taskDefNameMatInStoreB

    private val boxTypesAndGalleryQty = mapOf(
            "TW101" to 2, "TW102" to 2, "TW102H" to 2, "TW103" to 2
    )

    private fun getGalleryNameList(): List<String> {
        val list: MutableList<String> = mutableListOf()
        boxTypesAndGalleryQty.forEach { (bt, qty) ->
            var num = qty
            while (num > 0) {
                list.add("$bt-$num")
                num--
            }
        }
        return list
    }

    fun init() {
        if (CustomDataService.getCustomData().wtTakeSize.isEmpty()) {
            val newValue: MutableMap<String, Int> = mutableMapOf()
            getGalleryNameList().forEach { newValue["WT-$it"] = 2 }
            CustomDataService.setWTTakeSizeBatch(newValue, "init while no data")
        }
        executors.scheduleWithFixedDelay(this::createNewTasks, 2, 5, TimeUnit.SECONDS)
    }

    private fun createNewTasks() {
        try {
            createMatTransferTasks()
            createMatInStoreTasks()
        } catch (e: Exception) {
            logger.error("Create new task failed: ", e)
        }
    }

    /**
     * 批量创建物料转运任务（暂存区 -> 缓存区）
     */
    private fun createMatTransferTasks() {
        if (CustomDataService.getCustomData().stopCreateMatTransferTask) return

        try {
            getGalleryNameList().forEach { galleryName ->
                val wtGalleryName = "WT-$galleryName"
                val wbGalleryName = "WB-$galleryName"
                val position = galleryName.lastIndexOf("-")
                val type = galleryName.substring(0, position)
                // 对于当前类型的零件，如果存在还未结束的任务，则不继续创建任务
                if (matTransferTasksUnfinished(type)) return@forEach
                val a = CustomDataService.wtTakeSize
                val fromRequiredQty = CustomDataService.wtTakeSize[wtGalleryName]!!
                val fromSiteIds = findFromSiteIdsFromGallery(wtGalleryName, fromRequiredQty) ?: return@forEach
                logger.debug("============= fromSites: $fromSiteIds")

                val toRequiredQty = fromSiteIds.size
                val toSiteIds = findToSiteIdsFromWBGallery(wbGalleryName)
                logger.debug("============= toSites: $toSiteIds")

                // 终点数量不够，需要从关联的巷道中选择可用的终点库位
                val diff = toRequiredQty - toSiteIds.size
                if (diff > 0) {
                    val toSiteIdsAddition = getToSiteIdsFromRelatedGallery(galleryName)
                    logger.debug("============= toSitesAddition: $toSiteIdsAddition")
                    toSiteIds.addAll(toSiteIdsAddition)
                    logger.debug("============= allToSites: $toSiteIds")
                }

                if (toSiteIds.isEmpty()) return@forEach

                if (CUSTOM_CONFIG.doNotCreateTaskWhenSitesAvailable) return@forEach

                // 如果不存在对应的柔性任务，则报错。
                val taskDef = getRobotTaskDef(taskDefNameMatTransfer)
                        ?: throw BusinessError("不存在柔性任务【${taskDefNameMatTransfer}】")

                // 获取柔性任务中每个调度用的阶段的引用名，及相关运单的索引和阶段的索引
                val stageRefNameIndex = getStageRefNameAndIndexFromTaskDef(taskDef)
                // 根据起点和终点，锁定库位信息
                fromSiteIds.forEachIndexed fromLoop@{ index, from ->
                    // todo: fromSites 的数量超过 toSites 时，先不指定终点库位，在任务的第一个运单下发之前，再选择符合条件的终点库位
                    val toSiteId = if (index + 1 <= toSiteIds.size) toSiteIds[index] else ""
                    createNewTask(
                            TaskParams(taskDef, stageRefNameIndex, from, toSiteId, boardCheckSiteId, true)
                    )
                }
            }
        } catch (e: Exception) {
            logger.error("create new task[${taskDefNameMatTransfer}] failed: ", e)
        }
    }

    /**
     * 创建物料入库任务（缓存区 -> 立库入库口）
     */
    private fun createMatInStoreTasks() {
        if (CustomDataService.getCustomData().stopCreateMatInStoreTask) return

        listOf(true, false).forEach { bigTray ->
            val toSiteId = if (bigTray) "Big-Box-1" else "Small-Box-1"
            try {

                // 先判断任务终点（入库口）是否满足创建任务的条件
                if (!PlcListener.checkToSiteAvailable(bigTray)) return@forEach

                // 再判断各个巷道（起点）是否满足创建任务的条件
                val fromSiteId = findFromSiteIdsFromWBArea(bigTray) ?: return@forEach
//                val position = fromSiteId.lastIndexOf("-")
//                val type = fromSiteId.substring(0, position)
//                // 对于当前巷道的零件，如果存在还未结束的任务，则不继续创建任务
//                if (matInStoreTasksUnfinished(type)) return@forEach
                if (StoreSiteService.getStoreSiteById(fromSiteId)?.locked == true) return@forEach
                logger.debug("===================== fromSiteId=$fromSiteId")

                if (CUSTOM_CONFIG.doNotCreateTaskWhenSitesAvailable) return@forEach

                if (toSiteId == "Big-Box-1") {
                    // 如果不存在对应的柔性任务，则报错。
                    val taskDef = getRobotTaskDef(taskDefNameMatInStoreA)
                            ?: throw BusinessError("不存在柔性任务【${taskDefNameMatInStoreA}】")

                    // 获取柔性任务中每个调度用的阶段的引用名，及相关运单的索引和阶段的索引
                    val stageRefNameIndex = getStageRefNameAndIndexFromTaskDef(taskDef)

                    // 根据获取到的起点信息和终点信息，创建任务。
                    createNewTask(
                            TaskParams(taskDef, stageRefNameIndex, fromSiteId, toSiteId, "", false)
                    )
                } else {
                    // 如果不存在对应的柔性任务，则报错。
                    val taskDef = getRobotTaskDef(taskDefNameMatInStoreB)
                            ?: throw BusinessError("不存在柔性任务【${taskDefNameMatInStoreB}】")

                    // 获取柔性任务中每个调度用的阶段的引用名，及相关运单的索引和阶段的索引
                    val stageRefNameIndex = getStageRefNameAndIndexFromTaskDef(taskDef)

                    // 根据获取到的起点信息和终点信息，创建任务。
                    createNewTask(
                            TaskParams(taskDef, stageRefNameIndex, fromSiteId, toSiteId, "", false)
                    )
                }
            } catch (e: Exception) {
                logger.error("Create MatInStoreTasks of $toSiteId failed, ", e)
            }
        }
    }

    /**
     * 从目标区域的巷道中选择可用的起点库位。
     * @param galleryNameWithArea 例如 WT-TW103-1
     * @param requiredQty 期望获取的库位的数量，null表示返回所有库位id。
     * @return 如果没有符合条件的库位，则返回 null；否则返回非空的列表
     */
    private fun findFromSiteIdsFromGallery(galleryNameWithArea: String, requiredQty: Int?): List<String>? {
        val sitesOfGallery = StoreSiteService.listStoreSites()
                .filter { it.id.contains(galleryNameWithArea) }.sortedBy { it.id }
        val list: MutableList<Int> = mutableListOf()
        sitesOfGallery.forEachIndexed sogLoop@{ index, ss ->
            val currentSiteNum = index + 1
            if (!ss.locked && ss.filled) {
                // 只有未被锁定，且被占用的库位，才是可用的起点库位
                if (list.isEmpty()) list.add(currentSiteNum)
                else if (list.last() == index) list.add(currentSiteNum)     // 确保可用库位必须是连续的
                else return null
            } else {
                // 库位被锁定，则编号大于等于此库位编号的库位都不能被使用。
                if (ss.locked) return null
                if (!ss.filled && list.any { it < currentSiteNum }) {
                    // 库位未占用时，对于暂存区的巷道而言，如果之前已经选定了编号较小的库位，则此巷道的所有库位都不能被使用。
                    if (requiredQty != null) return null
                    // 库位未占用时，对于缓存区的巷道而言，直接返回目前可用的所有库位就行。
                    else return if (list.isEmpty()) null else list.map { "$galleryNameWithArea-$it" }
                }
                // 此时，说明当前库位是一个叉车可以通过的库位。
            }
            if (list.size == requiredQty) return list.map { "$galleryNameWithArea-$it" }
        }
        return if (list.isEmpty()) null else list.map { "$galleryNameWithArea-$it" }
    }

    /**
     * 从关联巷道（同箱型）中选择可用的库位。
     * @param galleryName like TW101-1
     * @return 关联巷道中符合条件的库位的id。
     */
    private fun getToSiteIdsFromRelatedGallery(galleryName: String): List<String> {
        val parts = galleryName.split("-")
        val number = parts.last().toInt()
        val relatedGalleryName = when (val boxType = parts.first()) {
            "TW103", "TW102", "TW102H", "TW101" -> getRelatedGalleryName(boxType, number)
            else -> throw BusinessError("undefined box type: $boxType")
        } ?: return emptyList()

        return findToSiteIdsFromWBGallery(relatedGalleryName)
    }

    private fun getRelatedGalleryName(boxType: String, number: Int): String {
        return "WB-$boxType-${if (number == 1) 2 else 1}"
    }

    /**
     * @param galleryNameWithArea like WB-TW101-1
     */
    fun findToSiteIdsFromWBGallery(galleryNameWithArea: String): MutableList<String> {
        // 对当前巷道按照库位名称进行倒序（desc）排列，优先判断编号较大的库位状态。
        val sitesOfGalleryDesc = StoreSiteService.listStoreSites()
                .filter { it.id.contains(galleryNameWithArea) }.sortedByDescending { it.id }

        val list: MutableList<Int> = mutableListOf()
        for (ss in sitesOfGalleryDesc) {
            // 向暂存区的巷道中放货时，一定是从编号较大的库位开始操作。
            val currentSiteNum = ss.id.split("-").last().toInt()
            if (!ss.locked && !ss.filled) {
                // 只有未被锁定，且未被占用的库位，才是可用的终点库位
                if (list.isEmpty()) list.add(currentSiteNum)
                else if (list.last() == currentSiteNum + 1) list.add(currentSiteNum)    // 确保可用库位必须是连续的
                else break  // 如果库位不是连续的。尝试返回可用的库位。
            } else {
                if (ss.locked) break    // 库位被锁定，则编号大于等于此库位编号的库位，都不能被使用。需要尝试返回可用的库位。
                if (ss.filled) break    // 库位被占用时，则编号大于等于此库位编号的库位，都不能被使用。需要尝试返回可用的库位。
                // 此时，说明当前库位是一个叉车可以通过的库位。
            }
        }
        // 从缓存区的巷道选择终点库位时，不能再有足够数量的满足状态的库位，就直接返回；否则继续遍历下一个巷道。
        return parseSiteIds(galleryNameWithArea, list).toMutableList()
    }

    /**
     * @param bigTray true:从TW103巷道中选择库位；false:从非TW103巷道中选择库位。
     * @return 符合条件的库位的id，如果没有则返回null。
     */
    private fun findFromSiteIdsFromWBArea(bigTray: Boolean): String? {
        // 根据要操作的容器的类型（大箱-big，小箱-small）选择库位的巷道
        val galleryNameWithAreaList = getGalleryNameFromArea(true, bigTray)

        // 从缓存区域选择起点库位的要求：
        // ① 优先选择满足库位的数量较少的巷道（尽快清空一个巷道）。
        // ② 满足条件①时，优先选择库位编号较大的巷道（让巷道尽快能放货）。
        // ③ 同时满足条件①和②时，按照巷道名称选择库位。
        // todo: 是否还需要可以通过接口或者配置文件，设置优先取哪个箱型的物料？？？
        val availableSites: MutableList<AvailableSitesCounts> = mutableListOf()
        galleryNameWithAreaList.forEach {
            val fromSiteIds = findFromSiteIdsFromGallery(it, null) ?: return@forEach
            if (fromSiteIds.isEmpty()) return@forEach
            availableSites.add(AvailableSitesCounts(fromSiteIds, fromSiteIds.size))
        }
        if (availableSites.isEmpty()) return null
        availableSites.sortBy { it.count }
        return availableSites.first().siteIds.first()
    }

    private fun getGalleryNameFromArea(fromWB: Boolean, bigTray: Boolean): List<String> {
        val area = if (fromWB) "WB" else "WT"
        return if (bigTray) {
            listOf("$area-TW103-1", "$area-TW103-2")
        } else {
            listOf("$area-TW101-1", "$area-TW101-2", "$area-TW102-1", "$area-TW102-2", "$area-TW102H-1", "$area-TW102H-2")
        }
    }

    /**
     * 防止重复创建物料转运任务
     * @param type 例如：TW103
     * @return true:对于当前类型的零件任务还没有全部结束，不能创建新的任务
     */
    private fun matTransferTasksUnfinished(type: String): Boolean {
        return RobotTaskService.listUnEditAbleTasksCache().values.any {
            it.def == taskDefNameMatTransfer
                    && getTaskSiteId(it, true).contains(type)
                    && getTaskSiteId(it, false).contains(type)
        }
    }

//    /**
//     * 防止重复创建物料入库任务。
//     * @param type 物料入库任务的起点库位巷道标志。
//     * @return true:当前巷道的转运任务还没有全部结束，不能创建新的任务。
//     */
//    private fun matInStoreTasksUnfinished(type: String): Boolean {
//        return RobotTaskService.listUnEditAbleTasksCache().values.any {
//            it.def == taskDefNameMatInStore
//                && getTaskSiteId(it, true).contains(type)
//        }
//    }

    /**
     * 获取任务的起点库位或者终点库位。
     * @param getFrom true:获取任务的起点库位id; false:获取任务的终点库位id
     */
    private fun getTaskSiteId(task: RobotTask, getFrom: Boolean): String {
        val field = if (getFrom) PvsFields.FromSiteId else PvsFields.ToSiteId
        val label = if (getFrom) "起点" else "终点"
        val siteId = task.persistedVariables[field]?.toString()
        if (siteId.isNullOrBlank()) throw BusinessError("获取任务【${task.id}】的${label}库位失败！")
        return siteId
    }

    /**
     * @param galleryNameWithArea 例如：WT-TW103-1。
     * @param siteNumList 库位编号的列表。
     * @return 库位id的列表，例如：[WT-TW103-1-1]
     */
    private fun parseSiteIds(galleryNameWithArea: String, siteNumList: List<Int>): List<String> {
        return siteNumList.map { "$galleryNameWithArea-$it" }.reversed()
    }

    /**
     * 获取柔性任务的调度用的阶段的引用名，及其对应的运单索引和阶段索引
     * @param taskDef 柔性任务的对象
     * @return key=阶段的引用名，value.tIndex=阶段所在运单的索引，value.sIndex=阶段在当前运单的索引
     */
    fun getStageRefNameAndIndexFromTaskDef(taskDef: RobotTaskDef): Map<String, StageIndex> {
        val stageRefNameAndIndex: MutableMap<String, StageIndex> = mutableMapOf()
        taskDef.transports.forEachIndexed transportLoop@{ tIndex, transport ->
            transport.stages.forEachIndexed stageLoop@{ sIndex, stage ->
                if (stage.forRoute != true) return@stageLoop
                val refName = stage.refName
                val newIndex = StageIndex(tIndex, sIndex)
                val oldIndex = stageRefNameAndIndex[refName]
                if (oldIndex != null)
                    throw BusinessError("柔性任务【${taskDef.name}】存在重复定义的阶段的引用名：$oldIndex, $newIndex")
                stageRefNameAndIndex[refName] = newIndex
            }
        }
        return stageRefNameAndIndex
    }

    private fun createNewTask(params: TaskParams) {
        val newTask = buildTaskInstanceByDef(params.taskDef)
        val fromSiteId = params.fromSiteId
        newTask.persistedVariables[PvsFields.FromSiteId] = fromSiteId
        newTask.persistedVariables[PvsFields.ToSiteId] = params.toSiteId
        StageRefNamePrefix.setStageLocation(newTask, params)

        newTask.priority = params.priority

        RobotTaskService.saveNewRobotTask(newTask)
    }
}

// 处理调度用的运单的索引名称，并根据索引的前缀，给对应的阶段设置工作站。
object StageRefNamePrefix {
    private const val PreFrom = "preFrom"       // 需要去起点前置点执行动作的引用名的前缀
    private const val From = "from"             // 需要去起点执行动作的引用名的前缀
    private const val PreTo = "preTo"           // 需要去终点前置点执行动作的引用名的前缀
    private const val Pre = "pre"           // 需要去终点前置点的前置点执行动作的引用名的前缀
    const val To = "to"                 // 需要去终点执行动作的引用名的前缀
    private const val BoardCheck = "bc"         // 需要去看板核对工位执行动作的引用名的前缀

    fun setStageLocation(task: RobotTask, params: TaskParams) {
        val position = params.fromSiteId.lastIndexOf("-")
        val type = params.fromSiteId.substring(0, position)
        val preFromSite = "PRE-${type}"
        val preToSite = "PRE-${params.toSiteId}"
        val preSite = "PRE2-${params.toSiteId}"
        val bcSiteId = params.boardCheckSiteId
        if (params.matTransfer) {
            // 设置物料转运任务的参数（暂存区 -> 缓存区）
            task.persistedVariables[PvsFields.BoardCheck] = bcSiteId
        } else {
            // 设置物料入库的任务的参数（缓存区 -> 入库口）
            task.persistedVariables[PvsFields.PreFromSiteId] = preFromSite
            task.persistedVariables[PvsFields.PreToSiteId] = preToSite
            task.persistedVariables[PvsFields.PreSiteId] = preSite
            task.persistedVariables[PvsFields.FromProps] = getFromProps(params.fromSiteId)
        }

        params.stageRefNameIndex.forEach { (refName, index) ->
            task.transports[index.tIndex].stages[index.sIndex].location =
                    when (val prefix = refName.split("_").first()) {
                        PreFrom -> preFromSite
                        From -> params.fromSiteId
                        PreTo -> preToSite
                        Pre -> preSite
                        To -> params.toSiteId
                        BoardCheck -> bcSiteId
                        else -> throw BusinessError("无法识别的阶段引用名称的前缀【$prefix】")
                    }
        }
    }

    /**
     * 根据起点库位中记录的货物信息，设置机器人在起点（WB）取货时的操作的属性
     * @param fromSiteId 缓存区的库位id。
     * @return 序列化后的操作的属性
     */
    private fun getFromProps(fromSiteId: String): String {
        val fromSite = StoreSiteService.getExistedStoreSiteById(fromSiteId)
        val content = fromSite.content
        val props: MutableMap<String, String> = mutableMapOf()
        // todo: build properties by content

        return props.toString()
    }
}

data class AvailableSitesCounts(
        val siteIds: List<String>,
        val count: Int
)

data class TaskParams(
        val taskDef: RobotTaskDef,
        val stageRefNameIndex: Map<String, StageIndex>,
        val fromSiteId: String,
        val toSiteId: String,
        val boardCheckSiteId: String,
        val matTransfer: Boolean,                           // true：物料转运，可能需要去看板核对工位
        val priority: Int = NewTaskListener.taskDefaultPriority
)

data class StageIndex(
        val tIndex: Int,        // 阶段所在运单的索引值
        val sIndex: Int         // 阶段在其所在运单的索引值
)

data class GalleryQtyInfo(
        val name: String,
        val takeQty: Int,
        val placeQty: Int
)

object PvsFields {
    const val PreFromSiteId = "PreFromSiteId"
    const val PreSiteId = "PreSiteId"
    const val FromSiteId = "fromSiteId"
    const val FromProps = "fromProps"
    const val PreToSiteId = "preToSiteId"
    const val ToSiteId = "toSiteId"
    const val BoardCheck = "boardCheckSiteId"
    const val TrayAvailable = "trayAvailable"
    const val SiteContent = "siteContent"
}